package org.gitee.weapp.service;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.gitee.weapp.domain.common.constant.Constant;
import org.gitee.weapp.domain.entity.LoginInfo;
import org.gitee.weapp.domain.entity.SysUser;
import org.gitee.weapp.domain.vo.LoginUser;
import org.gitee.weapp.mapper.LoginInfoMapper;
import org.gitee.weapp.mapper.SysUserMapper;
import org.gitee.weapp.utils.DateUtils;
import org.gitee.weapp.utils.IpUtils;
import org.gitee.weapp.utils.RedisCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.mobile.device.Device;
import org.springframework.mobile.device.DeviceUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class TokenService {

    private static final Integer MILLIS_SECONDS_TEN = 600;

    // 令牌自定义标识
    @Value("${token.header}")
    private String header;

    // 令牌秘钥
    @Value("${token.secret}")
    private String secret;

    // 令牌有效期（默认60分钟）
    @Value("${token.expireTime}")
    private int expireTime;

    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private LoginInfoMapper loginInfoMapper;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private HttpServletRequest request;

    private String getRedisOpenIdKey(String openId) {
        return Constant.KEY_PREFIX + openId;
    }

    private String getLoginDevice(Device device) {
        if (null == device) {
            return "unknown";
        }
        if (device.isMobile()) {
            return "mobile";
        } else if (device.isTablet()) {
            return "tablet";
        } else if (device.isNormal()) {
            return "pc";
        }
        return "unknown";
    }

    private void insertLoginInfo(String openId) {
        Device device = DeviceUtils.getCurrentDevice(request);
        String ip = IpUtils.getIpAddr(request);
        LoginInfo loginInfo = LoginInfo.builder().openId(openId)
                .loginIp(ip).loginDevice(getLoginDevice(device))
                .loginTime(DateUtils.getNowDate())
                .build();
        loginInfoMapper.insert(loginInfo);
        log.info("user {} login", openId);
    }

    /**
     * 创建token
     * @param openId      openId
     * @param sessionKey  sessionKey
     * @return token
     */
    public String createToken(String openId, String sessionKey, LoginUser loginUser) {
        SysUser sysUser = sysUserMapper.getSysUserByOpenId(openId);
        if (null == sysUser) {
            sysUser = SysUser.builder().openId(openId)
                    .userName(loginUser.getUserName())
                    .gender(loginUser.getGender())
                    .avatarUrl(loginUser.getAvatar())
                    .country(loginUser.getCountry())
                    .province(loginUser.getProvince())
                    .city(loginUser.getCity())
                    .registerTime(DateUtils.getNowDate()).build();

            sysUserMapper.insert(sysUser);
            log.info("user {} registered.", openId);
        }
        insertLoginInfo(openId);

        redisCache.setCacheObject(getRedisOpenIdKey(openId), sysUser, expireTime, TimeUnit.MINUTES);

        Map<String, Object> claims = new HashMap<>();
        claims.put("openId", openId);
        claims.put("sessionKey", sessionKey);
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(new Date(System.currentTimeMillis() + expireTime * 60 * 1000L))
                .signWith(SignatureAlgorithm.HS512, secret).compact();
    }


    private String getToken(HttpServletRequest request) {
        String token = request.getHeader(header);
        if (StringUtils.isNotEmpty(token) && token.startsWith(Constant.BEARER)) {
            token = token.replace(Constant.BEARER, "");
        }
        return token;
    }

    public SysUser getSysUserFromToken(HttpServletRequest request) {
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token)) {
            Claims claims = parseToken(token);
            if (null == claims) {
                return null;
            }
            // 解析对应的权限以及用户信息
            String openId = (String) claims.get("openId");
            return redisCache.getCacheObject(getRedisOpenIdKey(openId));
        }
        return null;
    }

    public void refreshToken(SysUser sysUser, boolean enfored) {
        long time = redisCache.getExpireTime(getRedisOpenIdKey(sysUser.getOpenId()));
        if (enfored || time <= MILLIS_SECONDS_TEN) {
            redisCache.setCacheObject(getRedisOpenIdKey(sysUser.getOpenId()), sysUser, expireTime, TimeUnit.MINUTES);
        }
    }

    public void deleteUser(SysUser sysUser) {
        redisCache.deleteCacheObject(getRedisOpenIdKey(sysUser.getOpenId()));
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    public Claims parseToken(String token) {
        try {
            return Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            log.error("解析token错误:{}", e.getMessage());
            return null;
        }
    }
}
